//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using MetroIoc;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.Logging;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Properties;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.ServiceLocation;

namespace Microsoft.Practices.Prism.MetroIocExtensions
{
	/// <summary>
	/// Base class that provides a basic bootstrapping sequence that
	/// registers most of the Composite Application Library assets
	/// in a <see cref="IContainer"/>.
	/// </summary>
	/// <remarks>
	/// This class must be overriden to provide application specific configuration.
	/// </remarks>
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
	public abstract class MetroIocBootstrapper : Bootstrapper
	{
		private bool useDefaultConfiguration = true;

		/// <summary>
		/// Gets the default <see cref="IContainer"/> for the application.
		/// </summary>
		/// <value>The default <see cref="IContainer"/> instance.</value>
		[CLSCompliant(false)]
		public IContainer Container { get; protected set; }

		/// <summary>
		/// Run the bootstrapper process.
		/// </summary>
		/// <param name="runWithDefaultConfiguration">If <see langword="true"/>, registers default Composite Application Library services in the container. This is the default behavior.</param>
		public override void Run(bool runWithDefaultConfiguration)
		{
			this.useDefaultConfiguration = runWithDefaultConfiguration;

			this.Logger = this.CreateLogger();
			if (this.Logger == null)
			{
				throw new InvalidOperationException(Resources.NullLoggerFacadeException);
			}

			this.Logger.Log(Resources.LoggerCreatedSuccessfully, Category.Debug, Priority.Low);

			this.Logger.Log(Resources.CreatingModuleCatalog, Category.Debug, Priority.Low);
			this.ModuleCatalog = this.CreateModuleCatalog();
			if (this.ModuleCatalog == null)
			{
				throw new InvalidOperationException(Resources.NullModuleCatalogException);
			}

			this.Logger.Log(Resources.ConfiguringModuleCatalog, Category.Debug, Priority.Low);
			this.ConfigureModuleCatalog();

			this.Logger.Log(Resources.CreatingMetroIocContainer, Category.Debug, Priority.Low);
			this.Container = this.CreateContainer();
			if (this.Container == null)
			{
				throw new InvalidOperationException(Resources.NullContainerException);
			}

			this.Logger.Log(Resources.ConfiguringUnityContainer, Category.Debug, Priority.Low);
			this.ConfigureContainer();

			this.Logger.Log(Resources.ConfiguringServiceLocatorSingleton, Category.Debug, Priority.Low);
			this.ConfigureServiceLocator();

			this.Logger.Log(Resources.ConfiguringRegionAdapters, Category.Debug, Priority.Low);
			this.ConfigureRegionAdapterMappings();

			this.Logger.Log(Resources.ConfiguringDefaultRegionBehaviors, Category.Debug, Priority.Low);
			this.ConfigureDefaultRegionBehaviors();

			this.Logger.Log(Resources.RegisteringFrameworkExceptionTypes, Category.Debug, Priority.Low);
			this.RegisterFrameworkExceptionTypes();

			this.Logger.Log(Resources.CreatingShell, Category.Debug, Priority.Low);
			this.Shell = this.CreateShell();
			if (this.Shell != null)
			{
				this.Logger.Log(Resources.SettingTheRegionManager, Category.Debug, Priority.Low);
				RegionManager.SetRegionManager(this.Shell, this.Container.Resolve<IRegionManager>());

				this.Logger.Log(Resources.UpdatingRegions, Category.Debug, Priority.Low);
				RegionManager.UpdateRegions();

				this.Logger.Log(Resources.InitializingShell, Category.Debug, Priority.Low);
				this.InitializeShell();
			}

				this.Logger.Log(Resources.InitializingModules, Category.Debug, Priority.Low);
				this.InitializeModules();

			this.Logger.Log(Resources.BootstrapperSequenceCompleted, Category.Debug, Priority.Low);
		}

		/// <summary>
		/// Configures the LocatorProvider for the <see cref="ServiceLocator" />.
		/// </summary>
		protected override void ConfigureServiceLocator()
		{
			ServiceLocator.SetLocatorProvider(() => this.Container.Resolve<IServiceLocator>());
		}

		/// <summary>
		/// Registers in the <see cref="IUnityContainer"/> the <see cref="Type"/> of the Exceptions
		/// that are not considered root exceptions by the <see cref="ExceptionExtensions"/>.
		/// </summary>
		protected override void RegisterFrameworkExceptionTypes()
		{
			base.RegisterFrameworkExceptionTypes();

			ExceptionExtensions.RegisterFrameworkExceptionType(
				typeof(MetroIoc.ResolutionException));
		}

		/// <summary>
		/// Configures the <see cref="IUnityContainer"/>. May be overwritten in a derived class to add specific
		/// type mappings required by the application.
		/// </summary>
		protected virtual void ConfigureContainer()
		{
			this.Container.RegisterInstance<ILoggerFacade>(Logger);
			this.Container.RegisterInstance<IContainer>(this.Container);
			this.Container.RegisterInstance(this.ModuleCatalog);

			if (useDefaultConfiguration)
			{
				RegisterType<IServiceLocator, MetroIocServiceLocatorAdapter>(true);
				RegisterType<IModuleInitializer, ModuleInitializer>(true);
				RegisterType<IModuleManager, ModuleManager>(true);
				RegisterType<RegionAdapterMappings, RegionAdapterMappings>(true);
				RegisterType<IRegionManager, RegionManager>(true);
				RegisterType<IEventAggregator, EventAggregator>(true);
				RegisterType<IRegionViewRegistry, RegionViewRegistry>(true);
				RegisterType<IRegionBehaviorFactory, RegionBehaviorFactory>(true);
				RegisterType<IRegionNavigationJournalEntry, RegionNavigationJournalEntry>(false);
				RegisterType<IRegionNavigationJournal, RegionNavigationJournal>(false);
				RegisterType<IRegionNavigationService, RegionNavigationService>(false);
				RegisterType<IRegionNavigationContentLoader, RegionNavigationContentLoader>(true);
			}
		}

		/// <summary>
		/// Initializes the modules. May be overwritten in a derived class to use a custom Modules Catalog
		/// </summary>
		protected override void InitializeModules()
		{
			IModuleManager manager;

			try
			{
				manager = this.Container.Resolve<IModuleManager>();
			}
			catch (ResolutionException ex)
			{
				if (ex.Message.Contains("IModuleCatalog"))
				{
					throw new InvalidOperationException(Resources.NullModuleCatalogException);
				}

				throw;
			}

			manager.Run();
		}

		/// <summary>
		/// Creates the <see cref="IContainer"/> that will be used as the default container.
		/// </summary>
		/// <returns>A new instance of <see cref="IContainer"/>.</returns>
		[CLSCompliant(false)]
		protected virtual IContainer CreateContainer()
		{
			return new MetroContainer();
		}

		/// <summary>
		/// Registers a type in the container only if that type was not already registered.
		/// </summary>
		/// <typeparam name="TFrom">The interface type to register.</typeparam>
		/// <typeparam name="TTo">The target type to register.</typeparam>
		/// <param name="registerAsSingleton">Registers the type as a singleton.</param>
		protected void RegisterType<TFrom, TTo>(bool registerAsSingleton) where TTo : TFrom
		{
			if (registerAsSingleton)
			{
				this.Container.Register<TFrom, TTo>(null, new SingletonLifecycle());
			}
			else
			{
				this.Container.Register<TFrom, TTo>();
			}
		}
	}
}